import { Injectable } from '@nestjs/common';
import { NodeSSH } from 'node-ssh';
import { Duplex } from 'stream';
@Injectable()
export class RemoteControlService {
    // 定义一个私有属性 sshSessions，它是一个 Map 类型的集合，用于存储 SSH 会话信息。
    // Map 的键是一个字符串，代表客户端的唯一标识符。
    // Map 的值是一个对象，包含以下属性：
    // ssh: NodeSSH 类型，表示一个 SSH 连接实例，用于执行 SSH 命令。
    // currentDirectory: 字符串类型，表示当前工作目录的路径。
    // homeDirectory: 字符串类型，表示用户的家目录路径。
    // shellStream: 可选的 Duplex 流类型，表示一个双向流，用于与 SSH 会话进行交互。
    private sshSessions = new Map<
        string,
        {
            ssh: NodeSSH;
            currentDirectory: string;
            homeDirectory: string;
            shellStream?: Duplex;
        }
    >();
    // 定义一个设备类型的字段，用于区分不同类型的设备
    private deviceType = 'linux'; // 默认设备类型为 Linux

    // 初始化会话
    initializeSession(clientId: string) {
        try {
            if (this.sshSessions.has(clientId)) {
                console.log(`会话${clientId}已存在`);
                return;
            }
            //  创建一个新的会话状态
            this.sshSessions.set(clientId, {
                ssh: new NodeSSH(),
                currentDirectory: '/',
                homeDirectory: '/',
                shellStream: undefined,
            });
            console.log(`会话${clientId}创建成功`);

        } catch (error) {
            console.error(`会话${clientId}创建失败`, error);
        }
    }

    //定义一个异步方法 startSSHSession，用于启动一个 SSH 会话
    async startSSHSession(
        host: string, // 主机地址
        username: string, // 用户名
        password: string, // 密码
        clientId: string, // 客户端ID
        type: string, // 设备类型
    ): Promise<string> {
        //设置设备类型
        this.deviceType = type;
        //检查会话是否已经初始化
        const session = this.sshSessions.get(clientId);
        if(!session){
            //断开连接
            this.disconnect(clientId);
            return "会话未初始化";
        }
        // 连接到 SSH 服务器
        try {
          await session.ssh.connect({
            host,
            username,
            password,
            port: 22,
            algorithms: {
                kex: [
                    'ecdh-sha2-nistp256',
                    'ecdh-sha2-nistp384',
                    'ecdh-sha2-nistp521',
                    'diffie-hellman-group-exchange-sha256',
                    'diffie-hellman-group14-sha1',
                    'diffie-hellman-group1-sha1', // 这是一个较旧的算法，安全性较低，最后尝试
                  ],
                  cipher: [
                    'aes128-ctr',
                    'aes192-ctr',
                    'aes256-ctr',
                    'aes128-gcm@openssh.com',
                    'aes256-gcm@openssh.com',
                    'aes128-cbc',
                    'aes192-cbc',
                    'aes256-cbc', // CBC 模式的算法安全性较低，建议谨慎使用
                  ],
                  hmac: [
                    'hmac-sha2-256',
                    'hmac-sha2-512',
                    'hmac-sha1', // SHA1 的 HMAC 也是较旧的选择
                  ],
            },
          }) 
           // 请求一个 shell 流，可以指定终端类型。终端类型决定了终端的行为和功能，比如字符编码、颜色支持等。
      // 常见的终端类型包括 'xterm', 'vt100', 'ansi' 等。
      // 'xterm' 是最常用的终端类型，支持颜色和鼠标事萨达阿萨
      // 'vt100' 是较旧的终端类型，功能较为基础。
      // 'ansi' 也是一种常见的终端类型，支持 ANSI 转义序列。
         const shellStream = await session.ssh.requestShell({
        term: 'xterm',
         });
         session.shellStream = shellStream; //保存shell流
         //如果是linux终端
         if(this.deviceType === 'linux'){
            // 执行命令获取用户的root目录，并去除两端的空白字符
        const homeDirectory = (
            await session.ssh.execCommand('echo $HOME')
          ).stdout.trim();
          session.currentDirectory = homeDirectory; // 当前目录设置为root目录
          session.homeDirectory = homeDirectory; // 家目录设置为root目录
         }else{
              // 如果是路由器或交换机，执行其他初始化设置
        // 例如：session.currentDirectory = '/'
         }
        // 返回一个字符串，表示 SSH 会话已经启动
      return `SSH 会话成功启动, 主机: ${host}, 用户: ${username}`
        } catch (error) {
            if (this.deviceType === 'device') {
                this.sendExitCommands(clientId);
              } else {
                this.disconnect(clientId);
              }
              
              return 'SSH 会话启动失败,失败原因：' + error.message
        }
    }

    /**
   * 根据客户端标识符获取对应的 SSH 会话的 shell 流。
   * 如果会话不存在或 shell 流未初始化，则返回 undefined。
   * @param clientId 客户端标识符
   * @returns 返回对应的 Duplex 流，如果会话不存在或 shell 流未初始化则返回 undefined。
   */
    getShellStream(clientId: string): Duplex | undefined {
        try {
          const session = this.sshSessions.get(clientId);
          if (!session) {
            console.error(`未找到客户端ID为 ${clientId} 的会话`);
            return undefined;
          }
          if (!session.shellStream) {
            console.error(`客户端ID为 ${clientId} 的会话中未初始化 shell 流`);
            return undefined;
          }
          return session.shellStream;
        } catch (error) {
          console.log('获取 shell 流时发生错误:', error);
          return undefined;
        }
    }



  async sendExitCommands(clientId: string): Promise<string> {
    const session = this.sshSessions.get(clientId);
    if (!session) {
      return '会话不存在';
    }
    if (!session.ssh.isConnected()) {
      return 'SSH连接已关闭';
    }
    if (session.shellStream && session.shellStream.writable) {
      try {
        // 监听错误事件
        session.shellStream.on('error', (error) => {
          console.error('Shell流错误:', error);
          this.cleanupSession(clientId);
        });
        // 监听关闭事件
        session.shellStream.on('close', () => {
          console.log('Shell 流已关闭');
          // 移除所有监听器
          session.shellStream.removeAllListeners();
          this.cleanupSession(clientId);
        });
 
        session.shellStream.on('data', (data) => {
          console.log('从路由器接收到的数据:', data.toString());
        });
        console.log('-----发送退出命令-----');
        // 发送退出命令
        // await session.ssh.execCommand('\x1A'); // Ctrl+Z
        // await session.ssh.execCommand('quit');
        session.shellStream.write('\x1A');
        // 等待一段时间以确保命令被处理，执行quit命令会导致Shell流关闭，从而触发 close 事件
        session.shellStream.write('quit\n');
        // 确保命令发送完成
        await new Promise((resolve) => setTimeout(resolve, 500));
        session.shellStream.end(); // 关闭写入流
        console.log('-----退出命令已发送-----');
        return '退出命令已发送';
      } catch (error) {
        console.error('设备执行退出命令时发生错误:', error);
        return '设备执行退出命令时发生错误';
      }
    } else {
      // 如果Shell流不可写或不存在，则清理会话
      this.cleanupSession(clientId);
      return 'Shell流不可写或不存在';
    }
  }
  // 只释放ssh连接，不释放shell流
  async cleanupSession(clientId: string): Promise<void> {
    const session = this.sshSessions.get(clientId);
    if (session && session.ssh.isConnected()) {
      try {
        session.ssh.dispose();
      } catch (error) {
        console.error('释放SSH连接时发生错误:', error);
      }
    }
    this.sshSessions.delete(clientId);
    console.log('cleanupSession SSH会话和资源已成功释放');
  }
  // 释放ssh连接和shell流
  async disconnect(clientId: string): Promise<string> {
    console.log(`设备断开: ${clientId}`);
    const session = this.sshSessions.get(clientId);
    if (!session) {
      return '会话不存在';
    }
    try {
      // 关闭 shell 流并清除监听器
      if (session.shellStream) {
        //监听流结束事件
        session.shellStream.end();
        session.shellStream.removeAllListeners();
      }
      // 释放 SSH 连接
      if (session.ssh.isConnected()) {
        try {
          session.ssh.dispose();
        } catch (disposeError) {
          console.error('释放 SSH 连接时发生错误:', disposeError);
        }
      }
      // 从映射中删除会话
      this.sshSessions.delete(clientId);
      console.log('disconnect SSH会话和资源已成功释放');
      return 'SSH会话和资源已成功释放';
    } catch (error) {
      console.error('断开连接时发生错误:', error);
      return '断开连接时发生错误';
    }
  }
}

